using System;
using System.Collections.Specialized;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Configuration;

namespace NET.Library.Web
{
    /// <summary>
    /// Class Name : SecureQueryString
    /// Author     : Martin Tomkins
    /// Synopsis   : Provides a secure means for transfering data within a query string.
    /// </summary>
    /// <example>
    /// Example 1: Reading querystring.
    /// if (Request.QueryString.Count > 0)
    /// {
    ///     SecureQueryString qs = new SecureQueryString(Request.QueryString["x"].ToString());
    ///     try
    ///     {
    ///         if (qs["PID"] != null)
    ///         {
    ///             i_projectId = Convert.ToInt32(qs["PID"]);
    ///         }
    ///     }
    ///     finally
    ///     {
    ///         qs = null;
    ///     }
    ///     
    /// Example 2: Writing querystring.
    /// SecureQueryString qs = new SecureQueryString();
    /// try
    /// {
    ///     qs["TCI"] = "6";
    ///     s_url = Request.ServerVariables["SCRIPT_NAME"] + "?x=" +
    ///             qs.ToString();
    /// }
    /// finally
    /// {
    ///     qs = null;
    /// }
    ///        link.NavigateUrl = s_url;
    /// </example>
    public class SecureQueryString : NameValueCollection
    {
        #region Private Variables
        // The key used for generating the encrypted string
        private const string _cryptoKey = "f1r5tch01c3";


        // The Initialization Vector for the DES encryption routine
        private readonly byte[] IV = new byte[8] { 240, 3, 45, 29, 0, 76, 173, 59 };
        #endregion

        #region Properties

        /// <summary>
        /// Returns the encrypted query string.
        /// </summary>
        public string EncryptedString
        {
            get
            {
                return HttpUtility.UrlEncode(Encrypt(Serialize()));
            }
        }

        /// <summary>
        /// Encryption key used in the encryption of the query string.
        /// </summary>
        /// <remarks>
        /// If an encryption key is specified in the web.config then that key is used
        /// otherwise the default key is used.
        /// </remarks>
        public string CryptoKey
        {
            get { return Config.GetConfigValue(ConfigurationManager.AppSettings["QueryStringCryptoKey"], _cryptoKey); }
        }
        #endregion

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        public SecureQueryString() : base() { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="encryptedString"></param>
        public SecureQueryString(string encryptedString)
        {
            encryptedString = encryptedString.Replace(" ", "+");
            Deserialize(Decrypt(encryptedString));
        }

        #endregion

        #region Modifiers
        /// <summary>
        /// Returns the EncryptedString property.
        /// </summary>
        public override string ToString()
        {
            return EncryptedString;
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Encrypts a serialized query string 
        /// </summary>
        private string Encrypt(string serializedQueryString)
        {
            byte[] buffer = Encoding.ASCII.GetBytes(serializedQueryString);
            TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
            MD5CryptoServiceProvider MD5 = new MD5CryptoServiceProvider();
            des.Key = MD5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(this.CryptoKey));
            des.IV = IV;
            return Convert.ToBase64String(
                des.CreateEncryptor().TransformFinalBlock(
                    buffer,
                    0,
                    buffer.Length
                )
            );
        }

        /// <summary>
        /// Decrypts a serialized query string
        /// </summary>
        private string Decrypt(string encryptedQueryString)
        {
            try
            {
                byte[] buffer = Convert.FromBase64String(encryptedQueryString);
                TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
                MD5CryptoServiceProvider MD5 = new MD5CryptoServiceProvider();
                des.Key = MD5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(this.CryptoKey));
                des.IV = IV;
                return Encoding.ASCII.GetString(
                    des.CreateDecryptor().TransformFinalBlock(
                        buffer,
                        0,
                        buffer.Length
                    )
                );
            }
            catch (CryptographicException)
            {
                HttpContext.Current.Response.Redirect(HttpContext.Current.Request.ServerVariables["SCRIPT_NAME"]);
                return string.Empty;
            }
            catch (FormatException)
            {
                HttpContext.Current.Response.Redirect(HttpContext.Current.Request.ServerVariables["SCRIPT_NAME"]);
                return string.Empty;
            }
        }

        /// <summary>
        /// Deserializes a decrypted query string and stores it
        /// as name/value pairs.
        /// </summary>
        private void Deserialize(string decryptedQueryString)
        {
            string[] nameValuePairs = decryptedQueryString.Split('&');
            for (int i = 0; i < nameValuePairs.Length; i++)
            {
                string[] nameValue = nameValuePairs[i].Split('=');
                if (nameValue.Length == 2)
                {
                    base.Add(nameValue[0], nameValue[1]);
                }
            }
        }

        /// <summary>
        /// Serializes the underlying NameValueCollection as a QueryString
        /// </summary>
        private string Serialize()
        {
            StringBuilder sb = new StringBuilder();
            foreach (string key in base.AllKeys)
            {
                sb.Append(key);
                sb.Append('=');
                sb.Append(base[key]);
                sb.Append('&');
            }

            return sb.ToString();
        }
        #endregion

    }
}